{
  "metadata" : {
    "id" : "b0521908-5d43-412f-9333-4ad140df4151",
    "name" : "occupancy_streaming_prediction.snb.ipynb",
    "user_save_timestamp" : "2018-08-05T18:46:32.287Z",
    "auto_save_timestamp" : "1970-01-01T01:00:00.000Z",
    "language_info" : {
      "name" : "scala",
      "file_extension" : "scala",
      "codemirror_mode" : "text/x-scala"
    },
    "trusted" : true,
    "sparkNotebook" : null,
    "customLocalRepo" : null,
    "customRepos" : null,
    "customDeps" : null,
    "customImports" : null,
    "customArgs" : null,
    "customSparkConf" : {
      "spark.sql.streaming.metricsEnabled" : true
    },
    "customVars" : null
  },
  "cells" : [ {
    "metadata" : {
      "id" : "BB5F9EE261C144B284C8C79701FD0DBE"
    },
    "cell_type" : "markdown",
    "source" : "# Inference of Room Occupancy using Environment Sensors\n## Part II. Predicting Occupancy on Real-Time Data"
  }, {
    "metadata" : {
      "id" : "28E49E3BA2AB47168BDD8D3D4A57E9CD"
    },
    "cell_type" : "markdown",
    "source" : "On [Part I](./occupancy_detection_model.snb.ipynb#), we trained and validated a Logistic Regression model\nusing labeled data of rooms fitted with ambient sensors. \nThe outcome of that process is a trained model that we saved to disk for further use.\n\nIn this notebook, using Structured Streaming, we are going to use that trained model to make predictions on streaming data."
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "C1FC39FF13874AD98DE9FE51214A9660"
    },
    "cell_type" : "code",
    "source" : [ "// These are shared definitions to locate the model\n", "val baseDir = \"/tmp\"  // Change this to an appropriate location\n", "val dataDir = s\"$baseDir/data\"\n", "val modelDir = s\"$baseDir/model\"\n", "val modelFile = s\"$modelDir/occupancy-lg.model\"" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "baseDir: String = /tmp\ndataDir: String = /tmp/data\nmodelDir: String = /tmp/model\nmodelFile: String = /tmp/model/occupancy-lg.model\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 5,
      "time" : "Took: 0.721s, at 2018-08-29 21:27"
    } ]
  }, {
    "metadata" : {
      "id" : "8CE1E4F5CA184A73B63940CB6646E881"
    },
    "cell_type" : "markdown",
    "source" : "### Load the previously trained model"
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "9EF0A1877B994F4E8B72F72E07071FFE"
    },
    "cell_type" : "code",
    "source" : [ "import org.apache.spark.ml._\n", "val model = PipelineModel.read.load(modelFile)" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "import org.apache.spark.ml._\nmodel: org.apache.spark.ml.PipelineModel = pipeline_026fbfe8ca4c\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 9,
      "time" : "Took: 1.016s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "presentation" : {
        "tabs_state" : "{\n  \"tab_id\": \"#tab2036446827-0\"\n}"
      },
      "id" : "5C7C1498725C4E58AE78BD24D5940EA3"
    },
    "cell_type" : "code",
    "source" : [ "model.stages" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "res14: Array[org.apache.spark.ml.Transformer] = Array(vecAssembler_530fd58b21e5, logreg_9b02575b4033)\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : "<div>\n      <script data-this=\"{&quot;dataId&quot;:&quot;anon23cdd08d647353a7b09806a2d2e95e9a&quot;,&quot;dataInit&quot;:[],&quot;genId&quot;:&quot;2036446827&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/req(['../javascripts/notebook/playground','../javascripts/notebook/magic/tabs'], \n      function(playground, _magictabs) {\n        // data ==> data-this (in observable.js's scopedEval) ==> this in JS => { dataId, dataInit, ... }\n        // this ==> scope (in observable.js's scopedEval) ==> this.parentElement ==> div.container below (toHtml)\n\n        playground.call(data,\n                        this\n                        ,\n                        {\n    \"f\": _magictabs,\n    \"o\": {}\n  }\n  \n                        \n                        \n                      );\n      }\n    );/*]]>*/</script>\n    <div>\n      <div>\n        <ul class=\"nav nav-tabs\" id=\"ul2036446827\"><li>\n              <a href=\"#tab2036446827-0\"><i class=\"fa fa-table\"/></a>\n            </li><li>\n              <a href=\"#tab2036446827-1\"><i class=\"fa fa-cubes\"/></a>\n            </li></ul>\n\n        <div class=\"tab-content\" id=\"tab2036446827\"><div class=\"tab-pane\" id=\"tab2036446827-0\">\n            <div>\n      <script data-this=\"{&quot;dataId&quot;:&quot;anon75ba38d597dce88b3b9bd6409ede7370&quot;,&quot;dataInit&quot;:[{&quot;uid&quot;:&quot;vecAssembler_530fd58b21e5&quot;},{&quot;uid&quot;:&quot;logreg_9b02575b4033&quot;}],&quot;genId&quot;:&quot;1694163197&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/req(['../javascripts/notebook/playground','../javascripts/notebook/magic/tableChart'], \n      function(playground, _magictableChart) {\n        // data ==> data-this (in observable.js's scopedEval) ==> this in JS => { dataId, dataInit, ... }\n        // this ==> scope (in observable.js's scopedEval) ==> this.parentElement ==> div.container below (toHtml)\n\n        playground.call(data,\n                        this\n                        ,\n                        {\n    \"f\": _magictableChart,\n    \"o\": {\"headers\":[\"uid\"],\"width\":600,\"height\":400}\n  }\n  \n                        \n                        \n                      );\n      }\n    );/*]]>*/</script>\n    <div>\n      <span class=\"chart-total-item-count\"><p data-bind=\"text: value\"><script data-this=\"{&quot;valueId&quot;:&quot;anoncd957c91c92ad0dfc521530ba6033680&quot;,&quot;initialValue&quot;:&quot;2&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/\nreq(\n['observable', 'knockout'],\nfunction (O, ko) {\n  ko.applyBindings({\n      value: O.makeObservable(valueId, initialValue)\n    },\n    this\n  );\n});\n        /*]]>*/</script></p> entries total</span>\n      <span class=\"chart-sampling-warning\"><p data-bind=\"text: value\"><script data-this=\"{&quot;valueId&quot;:&quot;anon8f3665e96a4db78a428f1e71ec46bb05&quot;,&quot;initialValue&quot;:&quot;&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/\nreq(\n['observable', 'knockout'],\nfunction (O, ko) {\n  ko.applyBindings({\n      value: O.makeObservable(valueId, initialValue)\n    },\n    this\n  );\n});\n        /*]]>*/</script></p></span>\n      <div>\n      </div>\n    </div></div>\n            </div><div class=\"tab-pane\" id=\"tab2036446827-1\">\n            <div>\n      <script data-this=\"{&quot;dataId&quot;:&quot;anon17f0e54f8f26c38f8ba5edbc7560fcb2&quot;,&quot;dataInit&quot;:[{&quot;uid&quot;:&quot;vecAssembler_530fd58b21e5&quot;},{&quot;coefficientMatrix&quot;:&quot;-0.1054579948898597  0.0  0.006732301245980496  0.001467376171685581  0.0  &quot;,&quot;margins&quot;:&quot;&lt;function1&gt;&quot;,&quot;score&quot;:&quot;&lt;function1&gt;&quot;,&quot;numFeatures&quot;:5,&quot;uid&quot;:&quot;logreg_9b02575b4033&quot;,&quot;interceptVector&quot;:&quot;[-1.3737536966101638]&quot;,&quot;trainingSummary&quot;:&quot;None&quot;,&quot;numClasses&quot;:2}],&quot;genId&quot;:&quot;133529074&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/req(['../javascripts/notebook/playground','../javascripts/notebook/magic/pivotChart'], \n      function(playground, _magicpivotChart) {\n        // data ==> data-this (in observable.js's scopedEval) ==> this in JS => { dataId, dataInit, ... }\n        // this ==> scope (in observable.js's scopedEval) ==> this.parentElement ==> div.container below (toHtml)\n\n        playground.call(data,\n                        this\n                        ,\n                        {\n    \"f\": _magicpivotChart,\n    \"o\": {\"width\":600,\"height\":400,\"derivedAttributes\":{},\"extraOptions\":{}}\n  }\n  \n                        \n                        \n                      );\n      }\n    );/*]]>*/</script>\n    <div>\n      <span class=\"chart-total-item-count\"><p data-bind=\"text: value\"><script data-this=\"{&quot;valueId&quot;:&quot;anonae2b2bad410a6fce186b492e93aa94fc&quot;,&quot;initialValue&quot;:&quot;2&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/\nreq(\n['observable', 'knockout'],\nfunction (O, ko) {\n  ko.applyBindings({\n      value: O.makeObservable(valueId, initialValue)\n    },\n    this\n  );\n});\n        /*]]>*/</script></p> entries total</span>\n      <span class=\"chart-sampling-warning\"><p data-bind=\"text: value\"><script data-this=\"{&quot;valueId&quot;:&quot;anonf42dab06b2303f3c0378b3fcee5b54da&quot;,&quot;initialValue&quot;:&quot;&quot;}\" type=\"text/x-scoped-javascript\">/*<![CDATA[*/\nreq(\n['observable', 'knockout'],\nfunction (O, ko) {\n  ko.applyBindings({\n      value: O.makeObservable(valueId, initialValue)\n    },\n    this\n  );\n});\n        /*]]>*/</script></p></span>\n      <div>\n      </div>\n    </div></div>\n            </div></div>\n      </div>\n    </div></div>"
      },
      "output_type" : "execute_result",
      "execution_count" : 10,
      "time" : "Took: 0.947s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "id" : "F801186BD67642E498563F9AC1276746"
    },
    "cell_type" : "markdown",
    "source" : "### Load the Data Stream\nFor this example, we are going to simulate a data stream with a trick.\nWe are going to create a built-in `rate` source to generate events at 1-second intervals and join those 'ticks' with data from our downloaded _dataset_. \n\nThis results in a regular stream of sample values. This can be easily replaced with a _Kafka_ or _File_ source for practical applications. \n\n>Note: We use this method because it is self-contained and does not require any additional setup of external dependencies."
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "F5AE8A8D1D2D439E835C9B69143EE4E5"
    },
    "cell_type" : "code",
    "source" : [ "val rateSource = sparkSession.readStream.format(\"rate\").load()" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "rateSource: org.apache.spark.sql.DataFrame = [timestamp: timestamp, value: bigint]\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 11,
      "time" : "Took: 0.671s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "8D7D907286BE412E9E377494C4F00B6C"
    },
    "cell_type" : "code",
    "source" : [ "val dataSource = sparkSession.read\n", "        .option(\"header\",true)\n", "        .option(\"inferSchema\", true)\n", "        .csv(s\"$dataDir/datatest2.txt\")" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "dataSource: org.apache.spark.sql.DataFrame = [id: int, date: timestamp ... 6 more fields]\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 12,
      "time" : "Took: 1.627s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "20D75A46350E4D70BF388423B5C8273C"
    },
    "cell_type" : "code",
    "source" : [ "dataSource.printSchema()" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "root\n |-- id: integer (nullable = true)\n |-- date: timestamp (nullable = true)\n |-- Temperature: double (nullable = true)\n |-- Humidity: double (nullable = true)\n |-- Light: double (nullable = true)\n |-- CO2: double (nullable = true)\n |-- HumidityRatio: double (nullable = true)\n |-- Occupancy: integer (nullable = true)\n\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 13,
      "time" : "Took: 0.696s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "31A1F011243547359602BF0BDCCF741A"
    },
    "cell_type" : "code",
    "source" : [ "val dataSize = dataSource.count" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "dataSize: Long = 9752\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 14,
      "time" : "Took: 0.930s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "6DE67033FDB34D068D2E51A8A9A6B2A9"
    },
    "cell_type" : "code",
    "source" : [ "// with the modulo operation, we circularly replay the data, creating a continuous data stream\n", "// for as long as the process executes\n", "val sensorDataStream = rateSource\n", "        .select($\"value\" % dataSize as \"id\", $\"timestamp\")\n", "        .join(dataSource, \"id\")" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "sensorDataStream: org.apache.spark.sql.DataFrame = [id: bigint, timestamp: timestamp ... 7 more fields]\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 15,
      "time" : "Took: 0.777s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "id" : "8A89B273E1C447CFBFCF96139245A711"
    },
    "cell_type" : "markdown",
    "source" : "### Predict occupancy on the sensor data using the trained model\nThe `PipelineModel` that we loaded can be directly applied to a streaming `DataFrame` using the `transform` function."
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "AD059B5BE6234B0F821814DB16160134"
    },
    "cell_type" : "code",
    "source" : [ "val scoredStream = model.transform(sensorDataStream)" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "scoredStream: org.apache.spark.sql.DataFrame = [id: bigint, timestamp: timestamp ... 11 more fields]\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 16,
      "time" : "Took: 0.808s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "187B7389727D466689A0C07699A23C66"
    },
    "cell_type" : "code",
    "source" : [ "scoredStream.printSchema" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "root\n |-- id: long (nullable = true)\n |-- timestamp: timestamp (nullable = true)\n |-- date: timestamp (nullable = true)\n |-- Temperature: double (nullable = true)\n |-- Humidity: double (nullable = true)\n |-- Light: double (nullable = true)\n |-- CO2: double (nullable = true)\n |-- HumidityRatio: double (nullable = true)\n |-- Occupancy: integer (nullable = true)\n |-- features: vector (nullable = true)\n |-- rawPrediction: vector (nullable = true)\n |-- probability: vector (nullable = true)\n |-- prediction: double (nullable = false)\n\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 17,
      "time" : "Took: 0.717s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "id" : "305665CC1F954FB08196A5FB060784A9"
    },
    "cell_type" : "markdown",
    "source" : "### Consume the predictions\nThe final step in our streaming prediction service is to do something with the prediction data.\nIn this notebook, we are going to limit this step to querying the data. \n\nFor real-world application, we will typically be interested in offering this service to other applications. Maybe in the form of an HTTP-based API or through pub/sub messaging interactions."
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "2C23F506FB1642B685CCB0A6BA329DB0"
    },
    "cell_type" : "code",
    "source" : [ "import org.apache.spark.sql.streaming.Trigger\n", "val query = scoredStream.writeStream\n", "        .format(\"memory\")\n", "        .queryName(\"memory_predictions\")\n", "        .start()" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "import org.apache.spark.sql.streaming.Trigger\nquery: org.apache.spark.sql.streaming.StreamingQuery = org.apache.spark.sql.execution.streaming.StreamingQueryWrapper@5bb26e2\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 18,
      "time" : "Took: 0.981s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "EEACCDE3189B40239D9F0347DF2ED91A"
    },
    "cell_type" : "code",
    "source" : [ "sparkSession.sql(\"select id, timestamp, occupancy, prediction from memory_predictions\")\n", ".show(15, false)" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "+---+---------+---------+----------+\n|id |timestamp|occupancy|prediction|\n+---+---------+---------+----------+\n+---+---------+---------+----------+\n\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : ""
      },
      "output_type" : "execute_result",
      "execution_count" : 19,
      "time" : "Took: 1.188s, at 2018-08-29 21:28"
    } ]
  }, {
    "metadata" : {
      "id" : "29C61ED1F7F745A788918822468DE8C1"
    },
    "cell_type" : "markdown",
    "source" : "Note that as we are using a test dataset for the predictions, we also have access to the actual\noccupancy data under the field \"Occupancy\""
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "E06F369871594EBBB96EC8A07660882C"
    },
    "cell_type" : "code",
    "source" : [ "query.status" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "res19: org.apache.spark.sql.streaming.StreamingQueryStatus =\n{\n  \"message\" : \"Waiting for data to arrive\",\n  \"isDataAvailable\" : false,\n  \"isTriggerActive\" : false\n}\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : "{\n  &quot;message&quot; : &quot;Waiting for data to arrive&quot;,\n  &quot;isDataAvailable&quot; : false,\n  &quot;isTriggerActive&quot; : false\n}"
      },
      "output_type" : "execute_result",
      "execution_count" : 13,
      "time" : "Took: 1.032s, at 2018-08-29 14:46"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : false,
      "id" : "B78156E44808432F88F89997D0D0F738"
    },
    "cell_type" : "code",
    "source" : [ "query.lastProgress" ],
    "outputs" : [ {
      "name" : "stdout",
      "output_type" : "stream",
      "text" : "res19: org.apache.spark.sql.streaming.StreamingQueryProgress =\n{\n  \"id\" : \"e62135ba-2bb9-41cf-83cb-8b7638592d8b\",\n  \"runId\" : \"014d2004-f9ad-40f4-867b-2ad77300ce4d\",\n  \"name\" : \"memory_predictions\",\n  \"timestamp\" : \"2018-08-19T10:42:33.496Z\",\n  \"batchId\" : 428,\n  \"numInputRows\" : 0,\n  \"inputRowsPerSecond\" : 0.0,\n  \"durationMs\" : {\n    \"getOffset\" : 0,\n    \"triggerExecution\" : 0\n  },\n  \"stateOperators\" : [ ],\n  \"sources\" : [ {\n    \"description\" : \"RateSource[rowsPerSecond=1, rampUpTimeSeconds=0, numPartitions=8]\",\n    \"startOffset\" : 427,\n    \"endOffset\" : 427,\n    \"numInputRows\" : 0,\n    \"inputRowsPerSecond\" : 0.0\n  } ],\n  \"sink\" : {\n    \"description\" : \"MemorySink\"\n  }\n}\n"
    }, {
      "metadata" : { },
      "data" : {
        "text/html" : "{\n  &quot;id&quot; : &quot;e62135ba-2bb9-41cf-83cb-8b7638592d8b&quot;,\n  &quot;runId&quot; : &quot;014d2004-f9ad-40f4-867b-2ad77300ce4d&quot;,\n  &quot;name&quot; : &quot;memory_predictions&quot;,\n  &quot;timestamp&quot; : &quot;2018-08-19T10:42:33.496Z&quot;,\n  &quot;batchId&quot; : 428,\n  &quot;numInputRows&quot; : 0,\n  &quot;inputRowsPerSecond&quot; : 0.0,\n  &quot;durationMs&quot; : {\n    &quot;getOffset&quot; : 0,\n    &quot;triggerExecution&quot; : 0\n  },\n  &quot;stateOperators&quot; : [ ],\n  &quot;sources&quot; : [ {\n    &quot;description&quot; : &quot;RateSource[rowsPerSecond=1, rampUpTimeSeconds=0, numPartitions=8]&quot;,\n    &quot;startOffset&quot; : 427,\n    &quot;endOffset&quot; : 427,\n    &quot;numInputRows&quot; : 0,\n    &quot;inputRowsPerSecond&quot; : 0.0\n  } ],\n  &quot;sink&quot; : {\n    &quot;description&quot; : &quot;MemorySink&quot;\n  }\n}"
      },
      "output_type" : "execute_result",
      "execution_count" : 14,
      "time" : "Took: 0.590s, at 2018-08-19 12:42"
    } ]
  }, {
    "metadata" : {
      "trusted" : true,
      "input_collapsed" : false,
      "collapsed" : true,
      "id" : "158400C83B4B4B809351834033EB868C"
    },
    "cell_type" : "code",
    "source" : [ "" ],
    "outputs" : [ ]
  } ],
  "nbformat" : 4
}